import os
import struct
import array
import time
import socket
import threading
import traceback
from datetime import datetime
from models.models import OltInfo
from django.db import close_old_connections

from tools.autoregister.autoregister import redis_oper
from tools.tools import write_log
from tools.Robot import sendTeam


def monitoring_state():
    oltrun_list = []

    while True:
        keys = redis_oper.conn.keys("*_run_uniqueid")
        if keys is None:
            time.sleep(2)
            continue
        new_oltrun_list = []
        start_th = []

        for key in keys:
            oltid = redis_oper.redis_get(key)
            if oltid not in oltrun_list:
                try:
                    close_old_connections()
                    olt_obj = OltInfo.objects.get(id=oltid)
                except OltInfo.DoesNotExist:
                    print("处：%s" % traceback.format_exc())
                    print("\/" * 100)
                    continue

                # 运行日志
                msg = "%s ping监控 开始" % olt_obj.olt_name
                write_log(data=msg, oltip=olt_obj.olt_ip, oltport=olt_obj.olt_port)

                lock = threading.Lock()
                th = threading.Thread(target=ping_command, args=(oltid, lock))
                redis_oper.conn.set("ping_%s" % olt_obj.olt_ip, "normal", 15)

                start_th.append(th)
                oltrun_list.append(oltid)
            new_oltrun_list.append(oltid)
        for i in start_th:
            i.start()

        oltrun_list = new_oltrun_list
        time.sleep(4)


def ping_command(oltid, lock):
    fallow_olt = set()
    s = AliveScan()
    disconnect_num = 0
    while True:
        close_old_connections()
        olt_obj = OltInfo.objects.filter(id=oltid)
        if not olt_obj.exists():
            return
        runstate = redis_oper.redis_get("%s_%s_run_uniqueid" % (olt_obj[0].olt_ip, olt_obj[0].olt_port))
        if runstate is None:
            lock.acquire()  # 上锁
            # logger.debug("%s ping监控 退出" % olt_obj[0].olt_name)
            lock.release()  # 释放锁
            return

        ip = olt_obj[0].olt_ip
        port = olt_obj[0].olt_port
        olt_name = olt_obj[0].olt_name
        alive_ip_set = set()

        alive_ips = s.hot_ping(set([ip, ]))
        if alive_ips:
            alive_ip_set = alive_ip_set | alive_ips

        # print("ping- %s"%(alive_ips))

        if ip != ''.join(alive_ip_set):
            redis_oper.conn.set("ping_%s" % olt_obj.olt_ip, "disconnect", 15)
            disconnect_num += 1
            if disconnect_num > 4 and ip not in fallow_olt:
                msg = "[{}]异常断开连接\n负责人请注意\n- 故障时间:{}\n- IP:{}".format(olt_name, str(datetime.now())[:19], ip)
                # 发送微信
                sendTeam(msg, 'robot')
                fallow_olt.add(ip)
                redis_oper.conn.set("disconnect_%s" % ip, str(datetime.now())[:19])
                # 运行日志
                write_log(data=msg, oltip=ip, oltport=port)
                lock.acquire()  # 上锁
                # logger.debug(msg)
                lock.release()  # 释放锁
        else:

            redis_oper.conn.set("ping_%s" % ip, "normal", 10)

            disconnect_num = 0
            if ip in fallow_olt:
                lock.acquire()  # 上锁
                # logger.debug("ip:%s" % ip)
                # logger.debug("fallow_olt:%s" % fallow_olt)
                lock.release()  # 释放锁

                disconnect_time = redis_oper.redis_get("disconnect_%s" % ip)
                if disconnect_time is None:
                    disconnect_time = ""
                fallow_olt.remove(ip)
                redis_oper.redis_del("disconnect_%s" % ip)
                msg = "[{}]连接已恢复\n- IP:{}\n- 故障时间:{}\n- 恢复时间:{}".format(olt_name, ip, disconnect_time,
                                                                        str(datetime.now())[:19])
                # 发送微信
                sendTeam(msg, 'robot')
                # 运行日志
                write_log(data=msg, oltip=ip, oltport=port)

                lock.acquire()  # 上锁
                # logger.debug(msg)
                lock.release()  # 释放锁
        alive_ip_set = set()
        time.sleep(2)


class SendPingThread(threading.Thread):
    def __init__(self, ipPool, icmpPacket, icmpSocket, timeout):
        threading.Thread.__init__(self)
        self.sock = icmpSocket
        self.ipPool = ipPool
        self.packet = icmpPacket
        self.timeout = timeout
        self.sock.settimeout(timeout + 1)

    def run(self):
        time.sleep(0.01)
        for ip in self.ipPool:
            try:
                self.sock.sendto(self.packet, (ip, 0))
                # 利用套截字发送icmp包
            except socket.timeout:
                break
        time.sleep(self.timeout)


class AliveScan:
    def __init__(self, timeout=2):
        self.timeout = timeout
        self._data = struct.pack('d', time.time())
        self._id = os.getpid()

    @property
    def icmp_socket(self):
        sock = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.getprotobyname("icmp"))
        return sock

    def check_sum(self, packet):
        if len(packet) & 1:
            packet = packet + '\0'
        words = array.array('h', packet)
        sum = 0
        for word in words:
            sum += (word & 0xffff)
        sum = (sum >> 16) + (sum & 0xffff)
        sum = sum + (sum >> 16)
        return (~sum) & 0xffff
        # 构造校验和

    @property
    def icmp_packet(self):
        header = struct.pack('bbHHh', 8, 0, 0, self._id, 0)
        packet = header + self._data
        chkSum = self.check_sum(packet)
        header = struct.pack('bbHHh', 8, 0, chkSum, self._id, 0)
        return header + self._data

    # 构造icmp包
    def hot_ping(self, ipPool):
        sock = self.icmp_socket
        sock.settimeout(self.timeout)
        packet = self.icmp_packet
        recvFroms = set()
        sendThr = SendPingThread(ipPool, packet, sock, self.timeout)
        sendThr.start()
        while True:
            try:
                # time_start = time.time()
                alive_ip = sock.recvfrom(1024)[1][0]
                # 接收返回值
                print("接收ping包")
                print("recvFroms", recvFroms)
                if alive_ip not in recvFroms:
                    # print('alive %s' % alive_ip)
                    recvFroms.add(alive_ip)
            except Exception as e:
                print("Ee", e)
            finally:
                if not sendThr.isAlive():
                    break
        print("recvFroms:", recvFroms)

        return recvFroms & ipPool


def get_alive_ip():
    s = AliveScan()
    return s
